`include "common.svh"

module alu (
    input clk,
    input rst,
    input i_valid,
    input ISSUE_OP i_iop,
    output i_ready,
    output o_valid,
    output ISSUE_OP o_iop,
    output word_t o_res,
    input o_ready
);

  localparam ISSUE_OP_SIZE = $size(ISSUE_OP);
  word_t src1, src2, res;
  FU_OP op;
  logic res_word;
  wire src_sign = (op == ALU_SRA) | (op == ALU_SLT);
  assign op = i_iop.rop.fop.dop.fu_op;
  assign src1 = res_word ? {{32{src_sign ? i_iop.src1[31] : 1'b0}}, i_iop.src1[31:0]} : i_iop.src1;
  assign src2 = res_word ? {{32{src_sign ? i_iop.src2[31] : 1'b0}}, i_iop.src2[31:0]} : i_iop.src2;
  assign res_word = i_iop.rop.fop.dop.is_op32;
  decouple inst_decouple (.*);
  wire i_fire = i_valid & i_ready;
  reg_l #(ISSUE_OP_SIZE) o_iop_r (
      .load(i_fire),
      .din (i_iop),
      .dout(o_iop),
      .*
  );
  reg_l #(`WORD_BITS) o_res_r (
      .load(i_fire),
      .din (res),
      .dout(o_res),
      .*
  );

  localparam SHIFT_BITS = $clog2(`WORD_BITS);
  wire [SHIFT_BITS-1:0] shift_amount = res_word ? {1'b0, src2[4:0]} : src2[SHIFT_BITS-1:0];
  always_comb begin
    res = 'b0;
    case (op)
      ALU_ADD:  res = src1 + src2;
      ALU_SUB:  res = src1 - src2;
      ALU_AND:  res = src1 & src2;
      ALU_OR:   res = src1 | src2;
      ALU_XOR:  res = src1 ^ src2;
      ALU_SLL:  res = src1 << shift_amount;
      ALU_SRA:  res = $signed(src1) >>> shift_amount;
      ALU_SRL:  res = src1 >> shift_amount;
      ALU_SLT:  res[0] = $signed(src1) < $signed(src2);
      ALU_SLTU: res[0] = src1 < src2;
      default:  res = 'b0;
    endcase
    if (res_word) res[`WORD_BITS-1:32] = {32{res[31]}};
  end
endmodule

module bru (
    input clk,
    input rst,
    input i_valid,
    input ISSUE_OP i_iop,
    output i_ready,
    output o_valid,
    output ISSUE_OP o_iop,
    output word_t o_res,
    output o_taken,
    output word_t o_dnpc,
    output BPU_Res o_correct_bpu_res,
    output o_bpred_miss,
    input o_ready
);

  localparam ISSUE_OP_SIZE = $size(ISSUE_OP);
  BranchType branch_type;

  word_t pc, src1, src2, imm, target_pc, res;
  FU_OP op;
  logic taken;
  assign op   = i_iop.rop.fop.dop.fu_op;
  assign pc   = i_iop.rop.fop.pc;
  assign src1 = i_iop.src1;
  assign src2 = i_iop.src2;
  assign imm  = `IMM_WORD(i_iop.rop.fop.dop.imm32);
  decouple inst_decouple (.*);
  wire i_fire = i_valid & i_ready;
  reg_l #(ISSUE_OP_SIZE) o_iop_r (
      .load(i_fire),
      .din (i_iop),
      .dout(o_iop),
      .*
  );
  reg_l #(`WORD_BITS) o_res_r (
      .load(i_fire),
      .din (res),
      .dout(o_res),
      .*
  );
  reg_l #(1) o_taken_r (
      .load(i_fire),
      .din (taken),
      .dout(o_taken),
      .*
  );
  reg_l #(`WORD_BITS) o_dnpc_r (
      .load(i_fire),
      .din (dnpc),
      .dout(o_dnpc),
      .*
  );

  word_t pnpc, dnpc, snpc;
  assign snpc = pc + 'h4;
  assign pnpc = i_iop.rop.fop.bpu_res_valid ? i_iop.rop.fop.bpu_res.target : snpc;
  assign dnpc = taken ? target_pc : snpc;
  wire bpred_miss = pnpc != dnpc;

  BPU_Res default_bpu_res;
  BPU_Res correct_bpu_res;
  wire rs1_link = i_iop.rop.fop.dop.rs1 == 'h1 || i_iop.rop.fop.dop.rs1 == 'h5;
  wire rd_link = i_iop.rop.fop.dop.rd == 'h1 || i_iop.rop.fop.dop.rd == 'h5;
  wire rs1_eq_rd = i_iop.rop.fop.dop.rs1 == i_iop.rop.fop.dop.rd;
  always_comb begin
    branch_type = BT_None;
    case (op)
      BRU_BEQ, BRU_BGE, BRU_BGEU, BRU_BLT, BRU_BLTU, BRU_BNE: branch_type = BT_Branch;
      BRU_JAL: branch_type = rd_link ? BT_Call : BT_Jump;
      BRU_JALR: begin
        if (!rd_link && rs1_link) branch_type = BT_Ret;
        else if (rd_link && !rs1_link) branch_type = BT_Call;
        else if (rd_link && rs1_link && !rs1_eq_rd) branch_type = BT_CallRet;
        else if (rd_link && rs1_link && rs1_eq_rd) branch_type = BT_Call;
        else branch_type = BT_Jump;
      end
      default: ;
    endcase

    default_bpu_res = 'b0;
    default_bpu_res.bi_cnt = taken ? 'h2 : 'h1;
    default_bpu_res.branch_type = branch_type;

    correct_bpu_res = i_iop.rop.fop.bpu_res_valid ? i_iop.rop.fop.bpu_res : default_bpu_res;

    correct_bpu_res.taken = taken;
    correct_bpu_res.target = dnpc;
    correct_bpu_res.branch_type = branch_type;
  end
  localparam BPU_RES_SIZE = $size(BPU_Res);
  reg_l #(BPU_RES_SIZE) o_correct_bpu_res_r (
      .load(i_fire),
      .din (correct_bpu_res),
      .dout(o_correct_bpu_res),
      .*
  );
  reg_next #(1) o_bpred_miss_r (
      .din (i_fire & bpred_miss),
      .dout(o_bpred_miss),
      .*
  );

  wire eq = src1 == src2;
  wire ne = !eq;
  wire ge = $signed(src1) >= $signed(src2);
  wire geu = src1 >= src2;
  wire lt = !ge;
  wire ltu = !geu;
  assign res = pc + 'h4;
  always_comb begin
    taken = 'b0;
    target_pc = pc + imm;
    case (op)
      BRU_BEQ:  taken = eq;
      BRU_BGE:  taken = ge;
      BRU_BGEU: taken = geu;
      BRU_BLT:  taken = lt;
      BRU_BLTU: taken = ltu;
      BRU_BNE:  taken = ne;
      BRU_JAL:  taken = 'b1;
      BRU_JALR: begin
        taken = 'b1;
        target_pc = src1 + imm;
      end
      default:  taken = 'b0;
    endcase
    target_pc[0] = 'b0;
  end
  // `PERF_EVENT(execute_branch_return, i_fire & branch_type == BT_Ret);
  // `PERF_EVENT(execute_branch_return_miss, i_fire & branch_type == BT_Ret & bpred_miss);
  // `PERF_EVENT(execute_branch_total, i_fire);
  // `PERF_EVENT(execute_branch_total_miss, i_fire & bpred_miss);
  // `PERF_EVENT(execute_branch_cond, (branch_type == BT_Branch) & (i_fire));
  // `PERF_EVENT(execute_branch_cond_miss, (branch_type == BT_Branch) & (i_fire) & bpred_miss);
endmodule

module agu (
    input clk,
    input rst,
    input i_valid,
    input ISSUE_OP i_iop,
    output i_ready,
    input i_rob_order_valid,
    input ROB_PTR i_rob_order_rob_idx,
    output o_valid,
    output ISSUE_OP o_iop,
    output MEM_REQ o_req,
    input o_ready
);

  localparam ISSUE_OP_SIZE = $size(ISSUE_OP);
  logic ld_valid;
  logic st_valid;
  word_t src1, src2, imm;
  FU_OP op  /* verilator public */;
  MEM_REQ mem_req;
  logic sign;
  logic [3:0] size;
  word_t addr, data;

  assign op   = i_iop.rop.fop.dop.fu_op;
  assign src1 = i_iop.src1;
  assign src2 = i_iop.src2;
  assign imm  = `IMM_WORD(i_iop.rop.fop.dop.imm32);
  wire mmio_ready = (o_req.mmio ? (i_rob_order_valid & (i_rob_order_rob_idx == o_req.rob_idx)) : 1'b1);
  wire decouple_o_valid;
  assign o_valid = decouple_o_valid & mmio_ready;
  decouple inst_decouple (
    .o_ready(o_ready & mmio_ready),
    .o_valid(decouple_o_valid),
    .*
  );
  wire i_fire = i_valid & i_ready;
  reg_l #(ISSUE_OP_SIZE) o_iop_r (
      .load(i_fire),
      .din (i_iop),
      .dout(o_iop),
      .*
  );
  localparam MEM_CMD_SIZE = $size(MEM_CMD);
  MUX_OH #(2, MEM_CMD_SIZE) mux_mem_req_cmd (
      .sel ({ld_valid, st_valid}),
      .din ({MEM_READ, MEM_WRITE}),
      .dout(mem_req.cmd)
  );

  assign mem_req.addr = addr;
  assign mem_req.data = data;
  assign mem_req.sign = sign;
  assign mem_req.size = size;
  assign mem_req.prf_idx = i_iop.rop.pdst;
  assign mem_req.rob_idx = i_iop.rob_idx;
  assign mem_req.mmio = addr[31:28] != 4'h8;
  assign mem_req.has_rd = i_iop.rop.fop.dop.has_rd;
  localparam MEM_REQ_SIZE = $size(MEM_REQ);
  reg_l #(MEM_REQ_SIZE) o_req_r (
      .load(i_fire),
      .din (mem_req),
      .dout(o_req),
      .*
  );

  logic [15:0] op_decode  /* verilator public */;
  decoder #(4) decoder_op (
      .din (op[3:0]),
      .dout(op_decode)
  );
  assign data = src2;
  assign sign = op_decode[LSU_LB[3:0]] | op_decode[LSU_LH[3:0]] | op_decode[LSU_LW[3:0]] | op_decode[LSU_LD[3:0]];
  assign size[0] = op_decode[LSU_LB[3:0]] | op_decode[LSU_LBU[3:0]] | op_decode[LSU_SB[3:0]];
  assign size[1] = op_decode[LSU_LH[3:0]] | op_decode[LSU_LHU[3:0]] | op_decode[LSU_SH[3:0]];
  assign size[2] = op_decode[LSU_LW[3:0]] | op_decode[LSU_LWU[3:0]] | op_decode[LSU_SW[3:0]];
  assign size[3] = op_decode[LSU_LD[3:0]] | op_decode[LSU_SD[3:0]];
  assign ld_valid  =  op_decode[LSU_LB[3:0]] | op_decode[LSU_LBU[3:0]]| op_decode[LSU_LH[3:0]] |
                        op_decode[LSU_LHU[3:0]] | op_decode[LSU_LW[3:0]] | op_decode[LSU_LWU[3:0]] | op_decode[LSU_LD[3:0]];
  assign st_valid = op_decode[LSU_SB[3:0]] | op_decode[LSU_SH[3:0]] | op_decode[LSU_SW[3:0]] | op_decode[LSU_SD[3:0]];
  assign addr = src1 + imm;
endmodule

import "DPI-C" function longint mul_caculate(
  bit is_mulh,
  bit src_word,
  longint src1,
  longint src2,
  bit src1_sign,
  bit src2_sign
);

import "DPI-C" function longint div_caculate(
  bit src_word,
  longint src1,
  longint src2,
  bit src_sign,
  bit is_rem
);

module mdu (
    input clk,
    input rst,
    input i_valid,
    input ISSUE_OP i_iop,
    output i_ready,
    output o_valid,
    output ISSUE_OP o_iop,
    output word_t o_res,
    input o_ready,
    output o_early_wakeup_valid,
    output PRF_IDX o_early_wakeup_prf_idx
);
  localparam MUL_LATENCY = 3;
  localparam ISSUE_OP_SIZE = $size(ISSUE_OP);
  FU_OP op  /* verilator public */;
  word_t src1, src2;
  logic src_word;
  assign op = i_iop.rop.fop.dop.fu_op;
  assign src1 = i_iop.src1;
  assign src2 = i_iop.src2;
  assign src_word = i_iop.rop.fop.dop.is_op32;
  logic [7:0] op_decode;
  decoder #(3) mdu_op_decoder (
      .din (op[2:0]),
      .dout(op_decode)
  );
  localparam [2:0] BIT_DIV = MDU_DIV[2:0];
  localparam [2:0] BIT_DIVU = MDU_DIVU[2:0];
  localparam [2:0] BIT_REMU = MDU_REMU[2:0];
  localparam [2:0] BIT_REM = MDU_REM[2:0];
  localparam [2:0] BIT_MUL = MDU_MUL[2:0];
  localparam [2:0] BIT_MULH = MDU_MULH[2:0];
  localparam [2:0] BIT_MULHSU = MDU_MULHSU[2:0];
  localparam [2:0] BIT_MULHU = MDU_MULHU[2:0];
  wire op_mul = op_decode[BIT_MUL] | op_decode[BIT_MULH] | op_decode[BIT_MULHSU] | op_decode[BIT_MULHU];
  wire op_div = op_decode[BIT_DIV] | op_decode[BIT_DIVU] | op_decode[BIT_REMU] | op_decode[BIT_REM];
  wire src1_sign = op_decode[BIT_MUL] | op_decode[BIT_MULH] | op_decode[BIT_MULHSU];
  wire src2_sign = op_decode[BIT_MUL] | op_decode[BIT_MULH];
  wire mulh = op_decode[BIT_MULH] | op_decode[BIT_MULHSU] | op_decode[BIT_MULHU];
  wire div_src_sign = op_decode[BIT_DIV] | op_decode[BIT_REM];
  wire div_is_rem = op_decode[BIT_REM] | op_decode[BIT_REMU];

  wire i_fire = i_valid & i_ready;
  logic div_ready, mul_ready;
  logic div_res_valid;

  ISSUE_OP mul_iop[MUL_LATENCY-1:0];
  logic mul_valid[MUL_LATENCY-1:0];
  word_t mul_res[MUL_LATENCY-1:0];

  assign mul_ready = (~mul_valid[0]) | o_ready;

  assign i_ready = mul_ready & div_ready;
  assign o_valid = mul_valid[MUL_LATENCY-1] | div_res_valid;
  assign o_early_wakeup_valid = (mul_valid[MUL_LATENCY-2] & mul_iop[MUL_LATENCY-2].rop.fop.dop.has_rd) & o_ready;
  assign o_early_wakeup_prf_idx = mul_iop[MUL_LATENCY-2].rop.pdst;
  genvar gi;
  generate
    logic valid_set[MUL_LATENCY-1:0];
    assign valid_set[0] = i_fire & op_mul;

    ISSUE_OP mul_iop_i[MUL_LATENCY-1:0];
    assign mul_iop_i[0] = i_iop;

    word_t mul_res_i[MUL_LATENCY-1:0];
    multiplier inst_multiplier (
        .res_high(mulh),
        .src_word(src_word),
        .src1_sign(src1_sign),
        .src2_sign(src2_sign),
        .src1(src1),
        .src2(src2),
        .res(mul_res_i[0])
    );
    // assign mul_res_i[0] = i_fire ? mul_caculate(
    //     mulh, src_word, src1, src2, src1_sign, src2_sign
    // ) : 'b0;

    for (gi = 1; gi < MUL_LATENCY; gi = gi + 1) begin : MUL_VALID
      assign valid_set[gi] = mul_valid[gi-1] & o_ready;
      assign mul_iop_i[gi] = mul_iop[gi-1];
      assign mul_res_i[gi] = mul_res[gi-1];
    end
    for (gi = 0; gi < MUL_LATENCY; gi = gi + 1) begin : MUL_UNIT
      reg_lr #(`WORD_BITS) mul_res_r (
          .load(valid_set[gi]),
          .din (mul_res_i[gi]),
          .dout(mul_res[gi]),
          .*
      );
      reg_lr #(ISSUE_OP_SIZE) mul_iop_r (
          .load(valid_set[gi]),
          .din (mul_iop_i[gi]),
          .dout(mul_iop[gi]),
          .*
      );
      reg_lr #(1) mul_valid_r (
          .load(valid_set[gi] | o_ready),
          .din (valid_set[gi]),
          .dout(mul_valid[gi]),
          .*
      );
    end

  endgenerate
  //DIV
  word_t div_res;
  divider inst_divider (
    .i_valid(i_valid & op_div),
    .i_ready(div_ready),
    .res_rem(div_is_rem),
    .src_word(src_word),
    .src_sign(div_src_sign),
    .src1(src1),
    .src2(src2),
    .o_valid(div_res_valid),
    .o_ready(o_ready),
    .res(div_res),
    .*
);
  ISSUE_OP div_iop;
  reg_l #(ISSUE_OP_SIZE) div_iop_r (
      .load(i_fire & op_div),
      .din (i_iop),
      .dout(div_iop),
      .*
  );
  assign o_iop = div_res_valid ? div_iop : mul_iop[MUL_LATENCY-1];
  assign o_res = div_res_valid ? div_res : mul_res[MUL_LATENCY-1];
endmodule


module misc (
    input clk,
    input rst,

    output o_rob_exeception_valid,
    output ROB_PTR o_rob_exeception_rob_idx,
    output EXPT_CODE o_rob_exeception_code,

    input i_rob_order_valid,
    input ROB_PTR i_rob_order_rob_idx,

    output o_rob_redirect_valid,
    output ROB_PTR o_rob_redirect_rob_idx,
    output word_t o_rob_redirect_pc,

    input i_valid,
    input ISSUE_OP i_iop,
    output i_ready,

    //Read/Write Port
    output CSR_ADDR csr_addr,
    output          csr_read,
    output          csr_write,
    output          csr_set,
    output          csr_clear,
    output word_t   csr_wdata,
    input  word_t   csr_rdata,
    input  word_t   csr_mepc,
    input  word_t   csr_mtvec,

    output o_valid,
    output ISSUE_OP o_iop,
    output word_t o_res,
    input o_ready
);

  localparam ISSUE_OP_SIZE = $size(ISSUE_OP);
  word_t src1, res;
  FU_OP op  /* verilator public */;
  logic [15:0] op_decode;
  decoder #(4) mdu_op_decoder (
      .din (op[3:0]),
      .dout(op_decode)
  );
  localparam [3:0] BIT_MISC_CSRRW = MISC_CSRRW[3:0];
  localparam [3:0] BIT_MISC_CSRRS = MISC_CSRRS[3:0];
  localparam [3:0] BIT_MISC_CSRRC = MISC_CSRRC[3:0];
  localparam [3:0] BIT_MISC_CSRRWI = MISC_CSRRWI[3:0];
  localparam [3:0] BIT_MISC_CSRRSI = MISC_CSRRSI[3:0];
  localparam [3:0] BIT_MISC_CSRRCI = MISC_CSRRCI[3:0];
  localparam [3:0] BIT_MISC_MRET = MISC_MRET[3:0];
  // localparam [3:0] BIT_MISC_EBREAK = MISC_EBREAK[3:0];
  localparam [3:0] BIT_MISC_ECALL = MISC_ECALL[3:0];
  assign op = i_iop.rop.fop.dop.fu_op;
  assign src1 = i_iop.src1;
  // always_comb $cast(csr_addr,i_iop.rop.fop.dop.imm32[11:0]);
  assign csr_addr = i_iop.rop.fop.dop.imm32[11:0];
  wire [4:0] rs1 = i_iop.rop.fop.dop.rs1;
  wire [4:0] rd = i_iop.rop.fop.dop.rd;
  wire i_fire = i_valid & i_ready;

  logic decouple_i_ready,decouple_i_valid;
  wire order_ready = i_rob_order_valid & (i_rob_order_rob_idx == i_iop.rob_idx);
  assign i_ready = decouple_i_ready & order_ready;
  assign decouple_i_valid = i_valid & order_ready;

  assign csr_read =  i_fire & (op_decode[BIT_MISC_CSRRS] | op_decode[BIT_MISC_CSRRSI] |op_decode[BIT_MISC_CSRRC] | op_decode[BIT_MISC_CSRRCI] |
   (op_decode[BIT_MISC_CSRRW] | op_decode[BIT_MISC_CSRRWI])) & (rd != 'b0);
  assign csr_write = i_fire & (op_decode[BIT_MISC_CSRRWI] | (op_decode[BIT_MISC_CSRRW] & (rs1 != 'b0)));
  assign csr_set = i_fire & (op_decode[BIT_MISC_CSRRSI] | (op_decode[BIT_MISC_CSRRS] & (rs1 != 'b0)));
  assign csr_clear = i_fire & (op_decode[BIT_MISC_CSRRCI] | (op_decode[BIT_MISC_CSRRC] & (rs1 != 'b0)));

  wire wdata_src1 = op_decode[BIT_MISC_CSRRS] | op_decode[BIT_MISC_CSRRC] | op_decode[BIT_MISC_CSRRW];
  wire wdata_rs1 = op_decode[BIT_MISC_CSRRSI] | op_decode[BIT_MISC_CSRRCI] | op_decode[BIT_MISC_CSRRWI];
  MUX_OH #(2, `WORD_BITS) mux_csr_wdata (
      .sel ({wdata_src1, wdata_rs1}),
      .din ({src1, {{(`WORD_BITS - 5) {1'b0}}, rs1}}),
      .dout(csr_wdata)
  );
  assign res = csr_rdata;
  decouple inst_decouple (.i_ready(decouple_i_ready), .i_valid(decouple_i_valid), .*);
  reg_l #(ISSUE_OP_SIZE) o_iop_r (
      .load(i_fire),
      .din (i_iop),
      .dout(o_iop),
      .*
  );
  reg_l #(`WORD_BITS) o_res_r (
      .load(i_fire),
      .din (res),
      .dout(o_res),
      .*
  );
  typedef enum EXPT_CODE {MCAUSE_ECALL_M = 'd11} MCAUSE;
  EXPT_CODE exeception_code;
  wire raise_exeception = op_decode[BIT_MISC_MRET] | op_decode[BIT_MISC_ECALL];
  assign exeception_code = MCAUSE_ECALL_M;
  assign o_rob_exeception_valid = i_fire & raise_exeception;
  assign o_rob_exeception_rob_idx = i_iop.rob_idx;
  assign o_rob_exeception_code = exeception_code;

  assign o_rob_redirect_valid = raise_exeception;
  assign o_rob_redirect_rob_idx = i_iop.rob_idx;
  MUX_OH #(2, `WORD_BITS) mux_o_rob_redirect_pc (
      .sel ({op_decode[BIT_MISC_MRET], op_decode[BIT_MISC_ECALL]}),
      .din ({csr_mepc, csr_mtvec}),
      .dout(o_rob_redirect_pc)
  );
endmodule



